/** 
 *  Yudit Unicode Editor Source File
 *
 *  GNU Copyright (C) 1997-2006  Gaspar Sinai <gaspar@yudit.org>  
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License, version 2,
 *  dated June 1991. See file COPYYING for details.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
 
#include "stoolkit/syntax/SHunspellPattern.h"
#include "stoolkit/SIO.h"
#include "stoolkit/SCharClass.h"

#ifdef USE_WINAPI
#include <windows.h>
#else
#include <stdlib.h>
#include <dlfcn.h>
#include <string.h>
#endif

static void fixFileName (SString& str);
static void convertRovasiras (const SV_UCS4& in, SV_UCS4& out);

static void fixFileName (SString& str)
{
#ifdef USE_WINAPI
  str.replaceAll ("/", "\\");
  if (str.size() > 0 && str[0] == '\\') str.remove (0);
#endif
}

static void* dynHandle = 0;

SHunspellPattern::SHunspellPattern (const SString& _name, 
  const SStringVector& path)
{
  name = _name;
  SString ps_dic = name;
  ps_dic.append (".dic");
  SFile fdic (ps_dic, path);
  if (fdic.size() < 0)
  {
    valid = false;
  }
  else
  {
    dicFile = fdic.getName ();
    fixFileName (dicFile);
  }

  SString ps_aff = name;
  ps_aff.append (".aff");
  SFile faff (ps_aff, path);
  if (faff.size() < 0)
  {
    valid = false;
  }
  else
  {
    affFile = faff.getName ();
    fixFileName (affFile);
  }


  dicFile.append ((char)0);
  affFile.append ((char)0);
  SString mising = loadLibrary (path);
  hunspell = 0;
  functions.create = 0;
  functions.spell = 0;
  functions.destroy = 0;
  functions.get_dic_encoding = 0;
  entryEncoder = 0;
  if (dynHandle)
  {
#ifdef USE_WINAPI
    functions.create = (void* (*)(const char*, const char*)) GetProcAddress((HMODULE)dynHandle, "hunspell_initialize");
    functions.spell = (int (*)(void*, const char*)) GetProcAddress((HMODULE)dynHandle, "hunspell_spell");
    functions.destroy = (int (*)(void*)) GetProcAddress((HMODULE)dynHandle, "hunspell_uninitialize");
    functions.get_dic_encoding = (char* (*)(void*))  GetProcAddress((HMODULE)dynHandle, "hunspell_get_dic_encoding");
#else
    functions.create = (void* (*)(const char*, const char*)) dlsym(dynHandle, "Hunspell_create");
    functions.spell = (int (*)(void*, const char*)) dlsym(dynHandle, "Hunspell_spell");
    functions.destroy = (int (*)(void*)) dlsym(dynHandle, "Hunspell_destroy");
    functions.get_dic_encoding = (char* (*)(void*))  dlsym(dynHandle, "Hunspell_get_dic_encoding");
#endif
    if (functions.create && functions.spell && functions.destroy &&
        functions.get_dic_encoding)
    {
      hunspell = (*functions.create)(affFile.array (), dicFile.array ());
      valid = (hunspell != 0);
    }
    else
    {
     fprintf (stderr, "hunspell: can not get function handles. create=%d spell=%d destroy=%d get_dic_encoding=%d\n",
         (int)(functions.create!=0),
         (int)(functions.spell!=0),
         (int)(functions.destroy!=0),
         (int)(functions.get_dic_encoding!=0)
      );
      functions.create = 0;
      functions.spell = 0;
      functions.destroy = 0;
      functions.get_dic_encoding = 0;
      valid = false;
    }
  }
  else
  {
    valid = false;
  }
  if (valid) {
    char* enc = (*functions.get_dic_encoding)(hunspell);
    // Get at least the ISO set that comes with Yudit.
    if (enc == 0) {
      entryEncoder = new SEncoder ("utf-8");
    } else if (strcmp (enc, "UTF-8") == 0) {
      entryEncoder = new SEncoder ("utf-8");
    } else if (strcmp (enc, "ISO8859-1") == 0) {
      entryEncoder = new SEncoder ("iso-8859-1");
    } else if (strcmp (enc, "ISO8859-2") == 0) {
      entryEncoder = new SEncoder ("iso-8859-2");
    } else if (strcmp (enc, "ISO8859-3") == 0) {
      entryEncoder = new SEncoder ("iso-8859-3");
    } else if (strcmp (enc, "ISO8859-4") == 0) {
      entryEncoder = new SEncoder ("iso-8859-4");
    } else if (strcmp (enc, "ISO8859-5") == 0) {
      entryEncoder = new SEncoder ("iso-8859-5");
    } else if (strcmp (enc, "ISO8859-6") == 0) {
      entryEncoder = new SEncoder ("iso-8859-6");
    } else if (strcmp (enc, "ISO8859-7") == 0) {
      entryEncoder = new SEncoder ("iso-8859-7");
    } else if (strcmp (enc, "ISO8859-8") == 0) {
      entryEncoder = new SEncoder ("iso-8859-8");
    } else if (strcmp (enc, "ISO8859-9") == 0) {
      entryEncoder = new SEncoder ("iso-8859-9");
    } else if (strcmp (enc, "ISO8859-13") == 0) {
      entryEncoder = new SEncoder ("iso-8859-13");
    } else if (strcmp (enc, "ISO8859-15") == 0) {
      entryEncoder = new SEncoder ("iso-8859-15");
    } else if (strcmp (enc, "ISO8859-16") == 0) {
      entryEncoder = new SEncoder ("iso-8859-16");
    } else if (strcmp (enc, "KOI8-R") == 0) {
      entryEncoder = new SEncoder ("koi8-r");
    } else {
      fprintf (stderr, "Need hunspell encoder for %s\n", enc);
      entryEncoder = new SEncoder ("utf-8");
    }
  }
}

SHunspellPattern::~SHunspellPattern ()
{
  if (functions.destroy && hunspell)
  {
    (*functions.destroy) (hunspell);
  }
  if (entryEncoder) delete entryEncoder;
}

SString
SHunspellPattern::loadLibrary (const SStringVector& path)
{
  if (dynHandle == 0)
  {
    SString libFile;
#ifdef USE_WINAPI
    libFile = "libhunspell.dll";
#else
#ifdef __APPLE__
    libFile = "libhunspell.dylib";
#else
    libFile = "libhunspell.so";
#endif
#endif
    SString ret = libFile;
    SFile flib (libFile, path);
    if (flib.size() > 0)
    {
      libFile = flib.getName();
      fixFileName (libFile);
    }
    libFile.append ((char)0);
#ifdef USE_WINAPI
    SEncoder u8("utf-8");
    SEncoder u16("utf-16-le");
    SString res = u16.encode (u8.decode (libFile));
    WCHAR* filenameW = (WCHAR *) res.array();
    dynHandle = LoadLibraryW (filenameW);
    if (!dynHandle) 
    {
      dynHandle = LoadLibraryA (libFile.array());
      if (!dynHandle) return SString(ret);
    }
#else
    dynHandle = dlopen (libFile.array(), RTLD_LAZY);
    if (!dynHandle) 
    {
      fprintf (stderr, "dlopen failed %s\n", ibFile.array());
      return SString(ret);
    }
#endif
    return SString ();
  }
  return SString ();
}

SString
SHunspellPattern::getFolderFor (const SString& name, 
  const SStringVector& path)
{
  for (unsigned int i=0; i<path.size(); i++)
  {
    SString f = path[i];
    f.append ("/");
    f.append (name);
    f.append (".dic");
    SFile file (f);
    if (file.size() > 0) return  SString (path[i]);
  }
  return SString ("");
}

// Return a non "" string in case there are some missing files
SString
SHunspellPattern::getMissingFile (const SString& name, 
  const SStringVector& path)
{
  SString missLib = loadLibrary (path);
  if (missLib != "") return SString (missLib);
  if (name == "")
  {
    return SString ("*.dic *.aff");
  }
  for (unsigned int i=0; i<path.size(); i++)
  {
    SString f_dic = path[i];
    f_dic.append ("/");
    f_dic.append (name);
    f_dic.append (".dic");
    SFile file_dic (f_dic);
    // aff file should be in the same directory
    if (file_dic.size() > 0)
    {
      SString f_aff = path[i];
      f_aff.append ("/");
      f_aff.append (name);
      f_aff.append (".aff");
      SFile file_aff (f_aff);
      if (file_aff.size() > 0) return (SString (""));
      f_aff = name;
      f_aff.append (".aff");
      return (SString (f_aff));
    }
  }
  SString ret = name;
  ret.append (".dic");
  return SString (ret);
}


// This method updates matchBegin and matchEnd (exclusive)
// variables in case of a match, in case of no match, this
// will not be touched. Action will contain that action string.
bool
SHunspellPattern::checkMatch ()
{
  if (current.size() == 0) return false;
  if (current.size() == 1) return false;
  // get the controls out first 
  SS_UCS4 chr = current[0];
  if (isNumber (chr))
  {
    action = ACT_NUMBER;
    matchBegin = matchEnd;
    matchEnd++;
    current.remove (0);
    return true;
  }
  if (isSeparator (chr) || chr == (SS_UCS4) '-')
  {
    action = ACT_CONTROL;
    matchBegin = matchEnd;
    matchEnd++;
    current.remove (0);
    return true;
  }
  if (isJapanese (chr))
  {
    action = ACT_NONE;
    matchBegin = matchEnd;
    matchEnd++;
    current.remove (0);
    return true;
  }
  unsigned int i;
  for (i=0; i<current.size(); i++)
  {
    if (isSeparator (current[i])) break;
  }
  if (i==current.size()) return false;
  if (i==0) return false; // never
  action = ACT_NONE;
  SV_UCS4 v = current;
  v.truncate (v.size() -1);

  matchBegin = matchEnd;
  matchEnd = matchBegin + v.size(); 
  for (i=0; i<v.size(); i++)
  {
    current.remove (0);
  }
  SV_UCS4 v1;
  convertRovasiras (v, v1);
  SString str =  (entryEncoder==0) ? SString ("E_R_R_O_R") : entryEncoder->encode (v1);
  str.append ((char) 0);
/*
fprintf (stderr, "call %08lx %08lx %s\n",
   (unsigned long) functions.spell, (unsigned long) hunspell, str.array());
*/
  if ((*functions.spell)(hunspell, str.array())==0)
  {
    action = ACT_ERROR;
  }
  return true;
}

// There is no spell checking for japanese/chinese
bool
SHunspellPattern::isJapanese (SS_UCS4 chr) const
{
  if (chr==SD_CD_ZWJ) return false;
  if (chr==SD_CD_ZWNJ) return false;
  SD_CharClass cc = getCharClass (chr);
  if (cc == SD_CC_Mn || cc == SD_CC_Me) return false;


  // CJK Symbols and Punctuation
  if (chr >= 0x3000 && chr <=0x303f) return true;
  // Hiragana
  if (chr >= 0x3040 && chr <=0x309f) return true;
  // Katakana
  if (chr >= 0x30A0 && chr <=0x30ff) return true;
  // 
  if (chr >= 0x31c0 && chr <=0x9fff) return true;

  // Japanes fullwidth alphabet
  if (chr >= 0xff21 && chr <=0xff5a) return false;

  // Halfwidth and Fullwidth Forms
  if (chr >= 0xff00 && chr <=0xffef) return true;
  // CJK Compatibility Ideographs
  if (chr >= 0xf900 && chr <=0xfaff) return true;
  // CJK Unified Ideographs Extension B
  if (chr >= 0x20000 && chr <=0x2A6DF) return true;
  // CJK Compatibility Ideographs Supplement  	
  if (chr >= 0x2F800 && chr <=0x2FA1F) return true;
  return false;
}

// This includes numbers
bool
SHunspellPattern::isSeparator (SS_UCS4 chr) const
{
  if (chr == 0xEE2F) return true; // HUNGARIAN RUNIC SEPARATOR in PUA

  if (chr==SD_CD_ZWJ) return false;
  if (chr==SD_CD_ZWNJ) return false; // this can be used within the word
  SD_CharClass cc = getCharClass (chr);
  // combined
  if (cc == SD_CC_Mn || cc == SD_CC_Me) return false;

  return (chr != (SS_UCS4) '-' 
       && cc != SD_CC_Lu && cc != SD_CC_Ll && cc != SD_CC_Lt 
       && cc != SD_CC_Lm && cc != SD_CC_Lo);
}
bool
SHunspellPattern::isNumber (SS_UCS4 chr) const
{
  SD_CharClass cc = getCharClass (chr);
  return (cc == SD_CC_Nd || cc == SD_CC_Nl || cc == SD_CC_No);
}

// Convert rovasiras into standard hungarian, all caps
static void
convertRovasiras (const SV_UCS4& in, SV_UCS4& out)
{
  SS_UCS4 chr;
  for (unsigned int i=0; i<in.size(); i++)
  {
    chr = in[i];
    switch  ((int)chr)
    {
    case 0xEE00: out.append ((SS_UCS4) 'A'); break;
    case 0xEE01: out.append ((SS_UCS4) 0xC1); break; // AA
    case 0xEE02: out.append ((SS_UCS4) 'B'); break;
    case 0xEE03: out.append ((SS_UCS4) 'C'); break;
    case 0xEE04: out.append ((SS_UCS4) 'C'); out.append ((SS_UCS4) 'S'); break;
    case 0xEE05: out.append ((SS_UCS4) 'D'); break;
    case 0xEE06: out.append ((SS_UCS4) 'E'); break; // AE
    case 0xEE07: out.append ((SS_UCS4) 'E'); break;
    case 0xEE08: out.append ((SS_UCS4) 0xC9); break; // EE
    case 0xEE09: out.append ((SS_UCS4) 'F'); break;
    case 0xEE0A: out.append ((SS_UCS4) 'G'); break;
    case 0xEE0B: out.append ((SS_UCS4) 'G'); out.append ((SS_UCS4) 'Y'); break;
    case 0xEE0C: out.append ((SS_UCS4) 'H'); break;
    case 0xEE0D: out.append ((SS_UCS4) 'I'); break;
    case 0xEE0E: out.append ((SS_UCS4) 0xCD); break; // II
    case 0xEE0F: out.append ((SS_UCS4) 'J'); break;
    case 0xEE10: out.append ((SS_UCS4) 'K'); break; // AK
    case 0xEE11: out.append ((SS_UCS4) 'K'); break; // EK
    case 0xEE12: out.append ((SS_UCS4) 'L'); break;
    case 0xEE13: out.append ((SS_UCS4) 'L'); out.append ((SS_UCS4) 'Y'); break;
    case 0xEE14: out.append ((SS_UCS4) 'M'); break;
    case 0xEE15: out.append ((SS_UCS4) 'N'); break;
    case 0xEE16: out.append ((SS_UCS4) 'N'); out.append ((SS_UCS4) 'Y'); break;
    case 0xEE17: out.append ((SS_UCS4) 'O'); break;
    case 0xEE18: out.append ((SS_UCS4) 0xD3); break; // OO
    case 0xEE19: out.append ((SS_UCS4) 0xD6); break; // OE
    case 0xEE1A: out.append ((SS_UCS4) 0x150); break; // OEE
    case 0xEE1B: out.append ((SS_UCS4) 'P'); break;
    case 0xEE1C: out.append ((SS_UCS4) 'R'); break;
    case 0xEE1D: out.append ((SS_UCS4) 'S'); break; // AS
    case 0xEE1E: out.append ((SS_UCS4) 'S'); break; // ES
    case 0xEE1F: out.append ((SS_UCS4) 'S'); out.append ((SS_UCS4) 'Z'); break;
    case 0xEE20: out.append ((SS_UCS4) 'T'); break;
    case 0xEE21: out.append ((SS_UCS4) 'T'); out.append ((SS_UCS4) 'Y'); break; // ATY
    case 0xEE22: out.append ((SS_UCS4) 'T'); out.append ((SS_UCS4) 'Y'); break; // ETY
    case 0xEE23: out.append ((SS_UCS4) 'U'); break;
    case 0xEE24: out.append ((SS_UCS4) 0xDA); break; // UU
    case 0xEE25: out.append ((SS_UCS4) 0xDC); break; // UE
    case 0xEE26: out.append ((SS_UCS4) 0x170); break; // UEE
    case 0xEE27: out.append ((SS_UCS4) 'V'); break;
    case 0xEE28: out.append ((SS_UCS4) 'Z'); break;
    case 0xEE29: out.append ((SS_UCS4) 'Z'); out.append ((SS_UCS4) 'S'); break; 
    case 0xEE2F: out.append ((SS_UCS4) ' '); break;
  
    // NUMBERS
    case 0xEE31: out.append ((SS_UCS4) '1'); break;
    case 0xEE35: out.append ((SS_UCS4) '5'); break;
    case 0xEE3A: out.append ((SS_UCS4) '1'); out.append ((SS_UCS4) '0'); break;
    case 0xEE3B: out.append ((SS_UCS4) '5'); out.append ((SS_UCS4) '0'); break;
    case 0xEE3C: out.append ((SS_UCS4) '1'); out.append ((SS_UCS4) '0'); out.append ((SS_UCS4) '0');break;
    case 0xEE3D: out.append ((SS_UCS4) '1'); out.append ((SS_UCS4) '0'); out.append ((SS_UCS4) '0'); out.append ((SS_UCS4) '0'); break;
  
    // LIGATURES
    case 0xEE40: out.append ((SS_UCS4) 'A'); out.append ((SS_UCS4) 'B'); break;
    case 0xEE41: out.append ((SS_UCS4) 'A'); out.append ((SS_UCS4) 'D'); break;
    case 0xEE42: out.append ((SS_UCS4) 'A'); out.append ((SS_UCS4) 'L'); break;
    case 0xEE43: out.append ((SS_UCS4) 'A'); out.append ((SS_UCS4) 'M'); out.append ((SS_UCS4) 'B'); break;
    case 0xEE44: out.append ((SS_UCS4) 'A'); out.append ((SS_UCS4) 'N'); out.append ((SS_UCS4) 'D'); break;
    case 0xEE45: out.append ((SS_UCS4) 'A'); out.append ((SS_UCS4) 'N'); out.append ((SS_UCS4) 'T'); break;
    case 0xEE46: out.append ((SS_UCS4) 'A'); out.append ((SS_UCS4) 'R'); break;
    case 0xEE47: out.append ((SS_UCS4) 'A'); out.append ((SS_UCS4) 'T'); out.append ((SS_UCS4) 'T'); break;
    // AAR
    case 0xEE48: out.append ((SS_UCS4) 0xC1); out.append ((SS_UCS4) 'R'); break;
    case 0xEE49: out.append ((SS_UCS4) 'B'); out.append ((SS_UCS4) 'A'); break;
    case 0xEE4A: out.append ((SS_UCS4) 'B'); out.append ((SS_UCS4) 'E'); break;
    case 0xEE4B: out.append ((SS_UCS4) 'B'); out.append ((SS_UCS4) 'E'); out.append ((SS_UCS4) 'T'); break;
    case 0xEE4C: out.append ((SS_UCS4) 'B'); out.append ((SS_UCS4) 'I'); break;
    case 0xEE4D: out.append ((SS_UCS4) 'B'); out.append ((SS_UCS4) 'O'); break;
    case 0xEE4E: out.append ((SS_UCS4) 'C'); out.append ((SS_UCS4) 'A'); out.append ((SS_UCS4) 'K'); break;
    case 0xEE4F: out.append ((SS_UCS4) 'C'); out.append ((SS_UCS4) 'K'); break;
  
    case 0xEE50: out.append ((SS_UCS4) 'C'); out.append ((SS_UCS4) 'S'); out.append ((SS_UCS4) 'A'); break;
    case 0xEE51: out.append ((SS_UCS4) 'C'); out.append ((SS_UCS4) 'S'); out.append ((SS_UCS4) 'I'); out.append ((SS_UCS4) 'N'); break;
    case 0xEE52: out.append ((SS_UCS4) 'D'); out.append ((SS_UCS4) 'U'); break;
    case 0xEE53: out.append ((SS_UCS4) 'E'); out.append ((SS_UCS4) 'M'); out.append ((SS_UCS4) 'P'); break;
    case 0xEE54: out.append ((SS_UCS4) 'E'); out.append ((SS_UCS4) 'N'); out.append ((SS_UCS4) 'T'); break;
    case 0xEE55: out.append ((SS_UCS4) 'G'); out.append ((SS_UCS4) 'A'); break;
    case 0xEE56: out.append ((SS_UCS4) 'G'); out.append ((SS_UCS4) 'E'); break;
    case 0xEE57: out.append ((SS_UCS4) 'G'); out.append ((SS_UCS4) 'I'); break;
    case 0xEE58: out.append ((SS_UCS4) 'G'); out.append ((SS_UCS4) 'O'); break;
    case 0xEE59: out.append ((SS_UCS4) 'H'); out.append ((SS_UCS4) 'A'); break;
    case 0xEE5A: out.append ((SS_UCS4) 'H'); out.append ((SS_UCS4) 'E'); break;
    case 0xEE5B: out.append ((SS_UCS4) 'H'); out.append ((SS_UCS4) 'I'); break;
    case 0xEE5C: out.append ((SS_UCS4) 'H'); out.append ((SS_UCS4) 'O'); break;
    case 0xEE5D: out.append ((SS_UCS4) 'I'); out.append ((SS_UCS4) 'T'); break;
    case 0xEE5E: out.append ((SS_UCS4) 'I'); out.append ((SS_UCS4) 'R'); out.append ((SS_UCS4) 'T'); break;
    case 0xEE5F: out.append ((SS_UCS4) 'L'); out.append ((SS_UCS4) 'A'); break;
  
    // LAA
    case 0xEE60: out.append ((SS_UCS4) 'L'); out.append ((SS_UCS4) 0xC1); break;
    case 0xEE61: out.append ((SS_UCS4) 'L'); out.append ((SS_UCS4) 'E'); break;
    case 0xEE62: out.append ((SS_UCS4) 'L'); out.append ((SS_UCS4) 'O'); break;
    case 0xEE63: out.append ((SS_UCS4) 'L'); out.append ((SS_UCS4) 'T'); break;
    case 0xEE64: out.append ((SS_UCS4) 'M'); out.append ((SS_UCS4) 'B'); break;
    case 0xEE65: out.append ((SS_UCS4) 'N'); out.append ((SS_UCS4) 'A'); break;
    case 0xEE66: out.append ((SS_UCS4) 'N'); out.append ((SS_UCS4) 'A'); out.append ((SS_UCS4) 'P'); break;
    case 0xEE67: out.append ((SS_UCS4) 'N'); out.append ((SS_UCS4) 'B'); break;
    case 0xEE68: out.append ((SS_UCS4) 'N'); out.append ((SS_UCS4) 'C'); break;
    case 0xEE69: out.append ((SS_UCS4) 'N'); out.append ((SS_UCS4) 'D'); break;
    case 0xEE6A: out.append ((SS_UCS4) 'N'); out.append ((SS_UCS4) 'G'); out.append ((SS_UCS4) 'Y'); break;
    case 0xEE6B: out.append ((SS_UCS4) 'N'); out.append ((SS_UCS4) 'I'); break;
    case 0xEE6C: out.append ((SS_UCS4) 'N'); out.append ((SS_UCS4) 'K'); break;
    case 0xEE6D: out.append ((SS_UCS4) 'N'); out.append ((SS_UCS4) 'T'); break;
    case 0xEE6E: out.append ((SS_UCS4) 'O'); out.append ((SS_UCS4) 'R'); break;
    case 0xEE6F: out.append ((SS_UCS4) 'R'); out.append ((SS_UCS4) 'A'); break;
  
  
    case 0xEE70: out.append ((SS_UCS4) 'R'); out.append ((SS_UCS4) 'E'); break;
    case 0xEE71: out.append ((SS_UCS4) 'R'); out.append ((SS_UCS4) 'I'); break;
    case 0xEE72: out.append ((SS_UCS4) 'R'); out.append ((SS_UCS4) 'O'); break;
    case 0xEE73: out.append ((SS_UCS4) 'R'); out.append ((SS_UCS4) 'T'); break;
    case 0xEE74: out.append ((SS_UCS4) 'R'); out.append ((SS_UCS4) 'U'); break;
    case 0xEE75: out.append ((SS_UCS4) 'S'); out.append ((SS_UCS4) 'A'); break;
    case 0xEE76: out.append ((SS_UCS4) 'S'); out.append ((SS_UCS4) 'E'); break;
    case 0xEE77: out.append ((SS_UCS4) 'S'); out.append ((SS_UCS4) 'I'); break;
    case 0xEE78: out.append ((SS_UCS4) 'S'); out.append ((SS_UCS4) 'K'); break;
    case 0xEE79: out.append ((SS_UCS4) 'S'); out.append ((SS_UCS4) 'M'); break;
    case 0xEE7A: out.append ((SS_UCS4) 'S'); out.append ((SS_UCS4) 'O'); break;
    case 0xEE7B: out.append ((SS_UCS4) 'S'); out.append ((SS_UCS4) 'P'); break;
    case 0xEE7C: out.append ((SS_UCS4) 'S'); out.append ((SS_UCS4) 'T'); break;
    case 0xEE7D: out.append ((SS_UCS4) 'S'); out.append ((SS_UCS4) 'Z'); out.append ((SS_UCS4) 'T'); break;
    case 0xEE7E: out.append ((SS_UCS4) 'T'); out.append ((SS_UCS4) 'I'); break;
    case 0xEE7F: out.append ((SS_UCS4) 'T'); out.append ((SS_UCS4) 'P'); out.append ((SS_UCS4) 'R'); out.append ((SS_UCS4) 'U'); break;
  
    case 0xEE80: out.append ((SS_UCS4) 'T'); out.append ((SS_UCS4) 'P'); out.append ((SS_UCS4) 'R'); out.append ((SS_UCS4) 'U'); out.append ((SS_UCS4) 'S'); break;
    case 0xEE81: out.append ((SS_UCS4) 'T'); out.append ((SS_UCS4) 'Y'); out.append ((SS_UCS4) 'A'); break;
    case 0xEE82: out.append ((SS_UCS4) 'U'); out.append ((SS_UCS4) 'L'); break;
    case 0xEE83: out.append ((SS_UCS4) 'U'); out.append ((SS_UCS4) 'M'); break;
    case 0xEE84: out.append ((SS_UCS4) 'U'); out.append ((SS_UCS4) 'N'); out.append ((SS_UCS4) 'K');break;
    case 0xEE85: out.append ((SS_UCS4) 'U'); out.append ((SS_UCS4) 'R'); break;
    case 0xEE86: out.append ((SS_UCS4) 'U'); out.append ((SS_UCS4) 'S'); break;
    case 0xEE87: out.append ((SS_UCS4) 'V'); out.append ((SS_UCS4) 'A'); break;
    // VAAR
    case 0xEE88: out.append ((SS_UCS4) 'V'); out.append ((SS_UCS4) 0xc1); out.append ((SS_UCS4) 'R'); break;
    case 0xEE89: out.append ((SS_UCS4) 'Z'); out.append ((SS_UCS4) 'A'); break;
    case 0xEE8A: out.append ((SS_UCS4) 'Z'); out.append ((SS_UCS4) 'R'); break;
    case 0xEE8B: out.append ((SS_UCS4) 'Z'); out.append ((SS_UCS4) 'T');  break;
    default: out.append (chr);
      break;
   }
 }
}
